package tool.analysis.series;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import tool.csv.CSVCompatible;
import tool.csv.FileWriter;

    /**
     * @author jwb09119
     * @date 2013/12/17
     * 
     * This analysis is concerned with the change in use of individual interfaces over time (i.e.
     * between versions).  The output of this analysis should be markers for each interface in each
     * version of the code base:
     * - a unique identifier
     * - if the interface exists at that time (boolean)
     * - if the interface is implemented anywhere in the code base
     * - if the interface is extended
     * 
     * This is probably as much detail I can get on an interface analysis without examining methods.
     */

public class InterfaceUseOverTimeAnalysis extends CSVCompatible implements ISeriesAnalysis {

    private final int DATAPOINTS = 6;
    //assert (DATAPOINTS > 0) : "Must have at least one data point!";
    
    // Row Key: [Present]   [Implementers][Extenders][ClassVar Use][MethodParam Use][LocalVar Use]
    // Row Key: [TRUE|FALSE][EMPTY|int]   [EMPTY|int][EMPTY|int]   [EMPTY|int]      [EMPTY|int]
    private Map<String, int[][]> records; 
    private final int TRUE = 1;
    private final int FALSE = 0;
    private final int EMPTY = -1;
    
    private List<String> experimentTitles;
    
    
    public InterfaceUseOverTimeAnalysis(FileWriter writer) {

        super(writer);
        experimentTitles = new ArrayList<>();
        records = new HashMap<String, int[][]>();
            
    }

    
    @Override
    public String getAnalysisTag () {

        
        return "Interface Use (Lifecycle)";
        
    }

    
    @Override
    public void analyseExperiments (Collection<ISeriesMember> experiments) {
        
        // Record the names of all experiments first
        for (ISeriesMember sm : experiments) {
            
            experimentTitles.add(sm.getSubjectName());
            
        }
        
        populateInterfaceRecords(experiments);
        

    }
    
    
    private void populateInterfaceRecords(Collection<ISeriesMember> experiments) {
        
        int experimentCounter = 0;
        
        for (ISeriesMember sm : experiments) {
            
            Set<String> interfaces = sm.getInterfaceNames();
            
            for (String iName : interfaces) {
                
                if (!records.containsKey(iName)) {
                    
                    processUnknownInterface(iName);
                    
                }
                // Either way, it is known now...    
                processKnownInterface(iName, experimentCounter, sm);
 
            }
            
            experimentCounter++;
            
        }
        
    }

    
    private void processUnknownInterface (String interfaceName) {

        records.put(interfaceName, getNewGrid());
           
    }
    
    
    private void processKnownInterface (String interfaceName, int experiment, ISeriesMember sm) {
        
        assert (records.containsKey(interfaceName)) : "Inconsistent records structure!";
       
        int[][] record = records.get(interfaceName);
        record[experiment][0] = TRUE;
        record[experiment][1] = sm.getImplementersCountFor(interfaceName);
        record[experiment][2] = sm.getExtendersCountFor(interfaceName);
        record[experiment][3] = sm.getUseAsClassVariableCount(interfaceName);
        record[experiment][4] = sm.getUseAsMethodParameterCount(interfaceName);
        record[experiment][5] = sm.getUseAsLocalVariableCount(interfaceName);
        //NB - row length limited by DATAPOINTS above
        
    }
    
    
    /**
     * Create an 'empty' array with space for all data that will be recorded.  Note that the array
     * is populated with 'default' values that preclude the need to check if an entry is empty.
     * @return String[][] with rows equal to the number of experiments, each wide enough to
     *         hold the number of data points being recorded. 
     */
    private int[][] getNewGrid () {
        
        int[][] reply = new int[experimentTitles.size()][DATAPOINTS];
        
        for (int i = 0; i < reply.length; i++) {
            
            reply[i][0] = FALSE;
            
            for (int j = 1; j < DATAPOINTS; j++) {
                
                reply[i][j] = EMPTY;
                
            }
            
        }
                
        return reply;
        
    }
    
    
    /**********************
     * CSV Export Methods *
     **********************/
    @Override
    protected String getExportHeader () {

        StringBuilder reply = new StringBuilder();
        
        reply.append("Interface lifecycle information for a sequence of code bases:\n");
        
        for (String t : experimentTitles) {
            reply.append("\t");
            reply.append(t);
            reply.append("\n");
        }
        
        return reply.toString();
        
    }

    @Override
    protected void writeStateToCSV (FileWriter writer) {
        
        StringBuilder header = new StringBuilder();
        header.append("InterfaceName");
        header.append(writer.SEPARATOR);
       
        for (String e : experimentTitles) {
            
            header.append(e+"_EXISTS");
            header.append(writer.SEPARATOR);
            header.append(e+"_IMPLEMENTED_COUNT");
            header.append(writer.SEPARATOR);
            header.append(e+"_EXTENDED_COUNT");
            header.append(writer.SEPARATOR);
            header.append(e+"_AS_CLASSVAR");
            header.append(writer.SEPARATOR);
            header.append(e+"_AS_METHODPARAM");
            header.append(writer.SEPARATOR);
            header.append(e+"_AS_LOCALVAR");
            header.append(writer.SEPARATOR);
            
            
        }
        
        writer.writeComment(header.toString());
        
        for (String s : records.keySet()) {
            
            int[][] rs = records.get(s);
            StringBuilder row = new StringBuilder(s);  // Construct with name already inserted
            row.append(writer.SEPARATOR);
            
            for (int[] r : rs) {                       // For each row in the record
                
                for (int i = 0; i < DATAPOINTS; i++) {
                
                    row.append(r[i]);                  // Append each data point in the row
                    
                    if (i < (DATAPOINTS-1)) {
                        
                        row.append(writer.SEPARATOR);
                        
                    }
                    
                }
                row.append(writer.SEPARATOR);          // Join table rows with commas
            }
            
            writer.writeComment(row.toString());
            
        }
        
    }

}
